package tech.waterism.utils.method;

import org.springframework.stereotype.Component;
import tech.waterism.dto.*;
import tech.waterism.entity.DHsh0;
import tech.waterism.entity.DZqSpillway;
import tech.waterism.entity.DZqSpillwayH;
import tech.waterism.utils.MathFun;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 蓄滞洪区调节，求泄流量
 */
@Component
public class StoreFlow {

    /**
     * 求对应流量的分洪设备口门宽
     *
     * @param z 当前水位
     * @param q 过流量
     */
    public static double getGateWidth(double z, double q, List<DSpillwaysDto> spillways) {
        // 求每一泄流设备在水位为z时的最大泄流量
        double gateWidth = 0;
        double h0 = z - spillways.get(0).getZcrest();

        if (h0 < 0) {
            return gateWidth;
        }

        gateWidth = q / (spillways.get(0).getZqMethods().get(0).getMflow() * Math.pow(h0, 1.5));
        return gateWidth;
    }

    /**
     * 求分洪设备在当前水位时的泄量
     *
     * @param z           当前水位
     * @param openGateNum 开启数量
     */
    public static double getOutFlow(double z, int openGateNum, List<DSpillwaysDto> spillways) {
        // 求每一泄流设备在水位为z时的最大泄流量
        return outflowFromOneSpillway(z, spillways.get(0), openGateNum);
    }

    /**
     * 求分洪设备在水位为z时的泄流量或最大泄流量（公式法或三点插值）
     *
     * @param z           当前水位
     * @param spillWay    SpillWay数据结构
     * @param openGateNum 闸门开启数量
     * @return 泄流量或最大泄流量（公式法或三点插值）
     */
    private static double outflowFromOneSpillway(double z, DSpillwaysDto spillWay, int openGateNum) {
        double qOut = 0;
        int nUsedGates[] = new int[spillWay.getNopenstates()];
        for (int iState = 0; iState < spillWay.getNopenstates(); iState++) {
            nUsedGates[iState] = openGateNum;
            if (nUsedGates[iState] > 0) {
                if (spillWay.getZclose() == -1) {
                    if (z > spillWay.getZcrest()) {
                        if (spillWay.getZqMethods().get(iState).getCalmethod() == 2) { // 判别用公式还是用3点插值求泄量
                            qOut = qOut +
                                    outflowFromOneByFormula(z, spillWay, iState) * nUsedGates[iState]; // ??每种形式的开度是否都相同
                        } else {
                            qOut = qOut + MathFun.insert3(z,
                                    spillWay.getZqMethods().get(iState).getZqSpillways().stream()
                                            .mapToDouble(DZqSpillway::getZ).toArray(),
                                    spillWay.getZqMethods().get(iState).getZqSpillways().stream()
                                            .mapToDouble(DZqSpillway::getQ).toArray(),
                                    spillWay.getZqMethods().get(iState).getZqSpillways().size()) * nUsedGates[iState];
                        }
                    }
                }
            }
        }
        return qOut;
    }

    /**
     * 求口门的流量
     *
     * @param z        当前水位
     * @param spillway 设备
     * @param iState   第i个开启状态
     */
    private static double outflowFromOneByFormula(double z, DSpillwaysDto spillway,
                                                  int iState) {// 参数意义:z为水位、aSpillWay为DS_SpillWay数据结构
        double qOut = 0;
        double h0 = z - spillway.getZcrest();
        if (h0 < 0) {
            return qOut;
        }
        qOut = spillway.getZqMethods().get(iState).getMflow() * spillway.getZqMethods().get(iState).getGatewidth() *
                Math.pow(h0, 1.5);
        return qOut;
    }

    /**
     * 获得蓄滞洪区设备信息
     *
     * @param spillwayType 1--分洪设备; 2--排洪设备; 3--分洪排洪设备
     */
    public static DSpillwaysDto getSpillway(int spillwayType, List<DSpillwaysDto> spillways) {
        DSpillwaysDto spillwayOne = null;
        for (int i = 0; i < spillways.size(); i++) {
            if (spillways.get(i).getSpillwaytype() == spillwayType) {
                spillwayOne = spillways.get(i);
            }
        }
        return spillwayOne;
    }

    /**
     * 求每一泄流设备在当前水位时的泄量
     *
     * @param zUp          河道水位
     * @param zDown        蓄滞洪区水位
     * @param spillwayType 设备类型
     * @param openGateNum  开启数量
     */
    public static double getOutFlow(double zUp, double zDown, List<DSpillwaysDto> spillways, int spillwayType,
                                    int openGateNum, List<DHsh0> hsh0s) {
        // 求每一泄流设备在水位为z时的最大泄流量
        DSpillwaysDto spillWayOne = getSpillway(spillwayType, spillways);
        return outflowFromOneSpillway(zUp, zDown, spillWayOne, openGateNum, hsh0s);
    }

    /**
     * 求每一泄流设备在水位为z时的泄流量或最大泄流量（公式法或三点插值）
     *
     * @param zUp         河道水位
     * @param zDown       蓄滞洪区水位
     * @param spillWay    SpillWay数据结构
     * @param openGateNum 闸门开启数量
     * @return 泄流量或最大泄流量（公式法或三点插值）
     */
    public static double outflowFromOneSpillway(double zUp, double zDown, DSpillwaysDto spillWay, int openGateNum,
                                                List<DHsh0> hsh0s) {
        double qOut = 0;
        int nUsedGates[] = new int[spillWay.getNopenstates()];
        for (int iState = 0; iState < spillWay.getNopenstates(); iState++) {
            nUsedGates[iState] = openGateNum;
            if (nUsedGates[iState] > 0) {
                if (spillWay.getZclose() == -1) {
                    if (zUp > spillWay.getZcrest() && zUp > spillWay.getZopen()) {
                        if (spillWay.getZqMethods().get(iState).getCalmethod() == 2) { // 判别用公式还是用3点插值求泄量
                            qOut = qOut + outflowFromOneByFormula(zUp, zDown, spillWay, openGateNum, iState,
                                    hsh0s); // ??每种形式的开度是否都相同
                        } else {
                            qOut = qOut + MathFun.insert3(zUp,
                                    spillWay.getZqMethods().get(iState).getZqSpillways().stream()
                                            .mapToDouble(DZqSpillway::getZ).toArray(),
                                    spillWay.getZqMethods().get(iState).getZqSpillways().stream()
                                            .mapToDouble(DZqSpillway::getQ).toArray(),
                                    spillWay.getZqMethods().get(iState).getZqSpillways().size()) * nUsedGates[iState];
                        }
                    }
                }
            }
        }
        return qOut;
    }

    /**
     * 求口门的流量
     *
     * @param zUp         河道水位
     * @param zDown       蓄滞洪区水位
     * @param spillWay    设备
     * @param openGateNum 开启数量
     * @param iState      第i个开启状态
     */
    private static double outflowFromOneByFormula(double zUp, double zDown, DSpillwaysDto spillWay, int openGateNum,
                                                  int iState, List<DHsh0> hsh0s) {// 参数意义:z为水位、aSpillWay为DS_SpillWay数据结构
        double qOut = 0;
        if (zUp < zDown) {
            return 0;
        }
        double h0 = zUp - spillWay.getZcrest();
        double hs = zDown - spillWay.getZcrest();
        double hsh0 = hs / h0;
        double s = 1;
        if (hsh0 > 0) {
            s = MathFun.insert3(hsh0, hsh0s.stream().mapToDouble(DHsh0::getHsh0).toArray(),
                    hsh0s.stream().mapToDouble(DHsh0::getS).toArray(), hsh0s.size());
        }
        if (h0 < 0) {
            return 0;
        }
        // 或许应该有这个条件????
        if (s > 1) {
            s = 1;
        }
        qOut = spillWay.getZqMethods().get(iState).getMflow() * s * openGateNum * Math.pow(h0, 1.5);
        return qOut;
    }

    public static double getGateWidth(double zUp, double zDown, double q, DSpillwaysDto spillWayOne,
                                      List<DHsh0> hsh0s) {
        double gateWidth = 0;
        double h0 = zUp - spillWayOne.getZcrest();
        double hs = zDown - spillWayOne.getZcrest();
        double hsh0 = hs / h0;
        double s = 1;
        if (hsh0 > 0) {
            s = MathFun.insert3(hsh0, hsh0s.stream().mapToDouble(DHsh0::getHsh0).toArray(),
                    hsh0s.stream().mapToDouble(DHsh0::getS).toArray(), hsh0s.size());
        }
        if (h0 < 0) {
            return 0;
        }
        gateWidth = q / (spillWayOne.getZqMethods().get(0).getMflow() * s * Math.pow(h0, 1.5));
        return gateWidth;
    }

    /**
     * 求分洪设备在当前水位/水位差时的泄量
     */
    public double getOutFlowH(double z, double h, int openGateNum, List<DSpillwaysDto> spillways) {
        // 求每一泄流设备在水位为z时的最大泄流量
        return outflowHFromOneSpillway(z, h, spillways.get(0), openGateNum);
    }

    /**
     * 求对应流量的口门宽
     *
     * @param zUp          河道水位
     * @param zDown        蓄滞洪区水位
     * @param spillwayType 设备类型
     * @param q            过流量
     */
    public double getGateWidth(double zUp, double zDown, int spillwayType, double q, List<DSpillwaysDto> spillways,
                               List<DHsh0> hsh0s) {
        // 求每一泄流设备在水位为z时的最大泄流量
        DSpillwaysDto spillWayOne = getSpillway(spillwayType, spillways);
        double gateWidth = 0;
        double h0 = zUp - spillWayOne.getZcrest();
        double hs = zDown - spillWayOne.getZcrest();
        double hsh0 = hs / h0;
        double s = 1;
        if (hsh0 > 0) {
            s = MathFun.insert3(hsh0, hsh0s.stream().mapToDouble(DHsh0::getHsh0).toArray(),
                    hsh0s.stream().mapToDouble(DHsh0::getS).toArray(), hsh0s.size());
        }
        if (h0 < 0) {
            return 0;
        }

        gateWidth = q / (spillWayOne.getZqMethods().get(0).getMflow() * s * Math.pow(h0, 1.5));

        return gateWidth;
    }

    /**
     * 检验口门宽度, 若超过最大宽度, 则返回最大宽度下的流量
     *
     * @param zUp          河道水位
     * @param zDown        蓄滞洪区水位
     * @param spillwayType 设备类型
     * @param q            过流量
     */
    public double checkGateWidth(double zUp, double zDown, int spillwayType, double q, int nGates,
                                 List<DSpillwaysDto> spillways, List<DHsh0> hsh0s) {
        double bcal = getGateWidth(zUp, zDown, spillwayType, q, spillways, hsh0s);
        double widthInGateMax = nGates;
        if (bcal > widthInGateMax) {
            double step = q / 2.0f;
            q = q - step;
            int index = 1;
            while ((widthInGateMax - bcal) > 10 || bcal > widthInGateMax) {
                // 向前跨一步搜
                bcal = getGateWidth(zUp, zDown, spillwayType, q, spillways, hsh0s);
                // 超过510,还需要向前跨一步
                if (bcal > widthInGateMax) {
                    if (index != 1) {
                        step = step * 0.5f;
                    }
                    q = q - step;
                    index = 1;
                }
                // 小于500, 过了, 需要往回走半步
                if ((510 - bcal) > 10) {
                    if (index != 2) {
                        step = step * 0.5f;
                    }
                    q = q + step;
                    index = 2;
                }
            }
        }
        return q;
    }

    /**
     * 检验分洪设备口门宽度, 若超过最大宽度, 则返回最大宽度下的流量
     *
     * @param z 当前水位
     * @param q 过流量
     */
    public double checkGateWidth(double z, double q, List<DSpillwaysDto> spillways) {
        double bcal = getGateWidth(z, q, spillways);
        double widthInGateMax = spillways.get(0).getNgates() * spillways.get(0).getZqMethods().get(0).getGatewidth();
        if (bcal > widthInGateMax) {
            q = getOutFlow(z, spillways.get(0).getNgates(), spillways);
        }
        return q;
    }

    /**
     * 求分洪设备在水位为z时,水位差为h时的泄流量或最大泄流量
     */
    private double outflowHFromOneSpillway(double z, double h, DSpillwaysDto spillWay, int openGateNum) {
        double qOut = 0;
        int nUsedGates[] = new int[spillWay.getNopenstates()];
        for (int iState = 0; iState < spillWay.getNopenstates(); iState++) {
            if (nUsedGates[iState] > 0) {
                if (spillWay.getZclose() == -1) {
                    if (z > spillWay.getZcrest()) {
                        List<DZqSpillwayH> zqSpillwayHs = spillWay.getZqMethods().get(iState).getZqSpillwayHs();
                        Map<Double, List<DZqSpillwayH>> groupByZ =
                                zqSpillwayHs.stream().collect(Collectors.groupingBy(DZqSpillwayH::getZ));
                        List<DZqSpillwayH> list1 = new ArrayList<>();
                        List<DZqSpillwayH> list2 = new ArrayList<>();
                        double z1 = 0.0;
                        double z2 = 0.0;
                        for (Map.Entry<Double, List<DZqSpillwayH>> entryByZ : groupByZ.entrySet()) {
                            Double zz = entryByZ.getKey();
                            List<DZqSpillwayH> listByZ = entryByZ.getValue();
                            if (z >= zz) {
                                list1 = listByZ;
                                z1 = zz;
                            } else {
                                list2 = listByZ;
                                z2 = zz;
                                break;
                            }
                        }
                        double q1 = MathFun.insert2(h, list1.stream().mapToDouble(DZqSpillwayH::getH).toArray(),
                                list1.stream().mapToDouble(DZqSpillwayH::getQ).toArray(), list1.size()) *
                                nUsedGates[iState];
                        if (list2.size() > 0) {
                            double q2 = MathFun.insert2(h, list2.stream().mapToDouble(DZqSpillwayH::getH).toArray(),
                                    list2.stream().mapToDouble(DZqSpillwayH::getQ).toArray(), list2.size()) *
                                    nUsedGates[iState];
                            qOut = qOut + q1 + (z - z1) / (z2 - z1) * (q2 - q1);
                        } else {
                            qOut = qOut + q1;
                        }
                    }
                }
            }
        }
        return qOut;
    }
}
